/*
 *  Copright: 2008 Philipp Leitner, Distributed Systems Group,
 *            TU Wien, leitner@infosys.tuwien.ac.at
 *
 *  This file is part of DAIOS.
 *
 *  DAIOS is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 * 
 *  DAIOS is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with DAIOS.  If not, see
 *  <http://www.gnu.org/licenses/>.
 * 
 */

package at.ac.tuwien.infosys.dsg.daios.wsdl.impl;

import java.math.BigInteger;
import java.util.List;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.SchemaLocalElement;
import org.apache.xmlbeans.SchemaParticle;
import org.apache.xmlbeans.SchemaType;

import at.ac.tuwien.infosys.dsg.daios.exceptions.ArrayException;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.AtomicTypesMapper;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLType;

/**
 * An abstract Daios WSDL type. Implements functionality
 * that all WSDL types should provide.
 * 
 * @author philipp
 *
 */
public abstract class WSDLType implements IWSDLType {

	protected QName name = null;
	protected IWSDLType[] seqTypes = null;
	
	/**
	 * Get the name of the type.
	 * 
	 * @return The element name of this type.
	 */
	public QName getName() {
		return name;
	}

	/**
	 * Get the content model of this type.
	 * 
	 * @return The content model as array of other
	 * type definitions.
	 */
	public IWSDLType[] getSequence() {
		return seqTypes;
	}

	/**
	 * Calculate the distance between this type (i.e. the structure
	 * of this type) and a given Daios message.
	 * 
	 * @param message The given Daios message.
	 * @return The distance between the structure of this type
	 * and the given Daios message.
	 */	
	public int calculateDistanceToMessage(DaiosMessage message) {
		return getStructure().structuralDistance(message);
	}
	
	/**
	 * Parse the content model of this type.
	 * 
	 * @param contentModel The content model as XMLBeans
	 * SchemaParticle.
	 * @param seqTypes We construct the content model in this list.
	 */
	protected void constructChildSequence(SchemaParticle contentModel,
			List<IWSDLType> seqTypes) {
		
		// we have no content model - return
		if(contentModel == null)
			return;
		
		// the content model consists only of one child,
		// e.g.
		// <sequence> <...sometype.../> </sequence>
		if(contentModel.getParticleChildren() == null)
			if(contentModel.getType() == null)
				return; // wildcard
			else
				seqTypes.add(new WSDLComplexType(contentModel.getName(),
						contentModel.getType()));
		
		// we have a more complex content model
		else {
			
			SchemaParticle[] particleChildren =
				contentModel.getParticleChildren();
			
			for(int i=0; i < particleChildren.length; i++) {
				if(particleChildren[i].getType() != null)
					seqTypes.add(new WSDLComplexType(particleChildren[i].getName(),
							particleChildren[i].getType()));
			}
		}
	}
	
	/**
	 * Parse a SchemaType to create its structure.
	 */
	protected void parseType(String name, SchemaType sType,
			DaiosMessage m, boolean array) {
		
		// this is a simple type
		if(sType.isSimpleType()) {
			
			String sVal = sType.getPrimitiveType().getName().getLocalPart();
			
			// we have to treat decimals a little special
			if(sVal.equals("decimal"))
				sVal = getConcreteDecimalValue(sType.getDecimalSize());
			
			// set this simple field into the structure message
			try {
				Class c = null;
				if(array) {
					c = AtomicTypesMapper.get(sVal);
					m.set(name, AtomicTypesMapper.getArrayDefault(c),
							AtomicTypesMapper.getArray(sVal));
				}
				else {
					c = AtomicTypesMapper.get(sVal);
					m.set(name, AtomicTypesMapper.getDefault(c), c);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		// this is a complex type
		else {
			
			// create a new submessage
			DaiosMessage subM = new DaiosMessage();
			if(array) {
				try {
					m.setComplexArray(name, new DaiosMessage[] {subM});
				} catch (ArrayException e) {
					e.printStackTrace();
				}
			}
			else
				m.setComplex(name, subM);
			
			if(sType.getContentModel() == null)
				// builtin type
				return;
			
			// parse this submessage
			SchemaParticle[] particles = sType.getContentModel().getParticleChildren();
			SchemaLocalElement localEl = null;
			if(particles == null) {
				
				if(!(sType.getContentModel() instanceof SchemaLocalElement))
					// builtin type
					return;
				
				localEl = (SchemaLocalElement)sType.getContentModel();
				parseType(localEl.getName().getLocalPart(),
						localEl.getType(), subM,
						isArray(localEl));
			}
			else {
				for(SchemaParticle p: particles) {
					if(!(p instanceof SchemaLocalElement))
						// builtin type
						continue;
					localEl = (SchemaLocalElement)p;
					parseType(localEl.getName().getLocalPart(),
							localEl.getType(), subM,
							isArray(localEl));
				}
			}
		}
	}
	
	/**
	 * Check if this schema element is an array element
	 * (if it has a maxOccurs unequal 1).
	 * 
	 * @param element The element to check.
	 * @return True if the element is an array element, false
	 * otherwise.
	 */	
	protected boolean isArray(SchemaLocalElement element) {
		
		boolean isArray = (element.getMaxOccurs() == null) ||
			!(element.getMaxOccurs().equals(BigInteger.ONE));
		
		return isArray;
	}
	
	/**
	 * A helper to 'translate' the value 'decimal' used by
	 * XMLBeans.
	 * 
	 * @param size The size of the decimal.
	 * @return The concrete subtype of decimal that corresponds
	 * to the given size.
	 */	
	protected static String getConcreteDecimalValue(int size) {
		
		if(size == 32)
			return "int";
		if(size == 64)
			return "long";	
		if(size == 16)
			return "short";	
		
		return "decimal";
	}
	
}
